﻿using System;
using System.Diagnostics;
using System.Threading;
using Red.Scripts.Defines.Utilities;

namespace Red.Scripts.Defines.Scc
{
    public class Launcher : Log.Emitter
    {
        #region Private Data

        private int _isProcessBeingCreated;
        private object _sync = new object();
        private Process _process;

        #endregion Private Data

        #region Private Properties

        private bool IsProcessBeingCreated
        {
            get
            {
                return Interlocked.CompareExchange( ref _isProcessBeingCreated, 1, 1 ) == 1;
            }

            set
            {
                Interlocked.Exchange( ref _isProcessBeingCreated, value ? 1 : 0 );
            }
        }

        #endregion Private Properties

        #region Public Properties

        public int Port { get; set; } = 45678;

        public string PathToExe { get; private set; }
        public bool CouldNotFindExe { get; private set; }

        public bool IsRunning => IsRunningInternal() && !HasExited();

        public Locator.Data SearchData { get; private set; } = new Locator.Data() { Filename = "scc.exe" };

        #endregion Public Properties

        #region Public Methods

        public void Launch()
        {
            if ( IsRunning )
                return;

            if ( !FindCompilerExecutablePath() )
            {
                WriteLog( "ERROR: Failed to locate SCC! Make sure you have custom path set in Tools > Options > Red Scripts > Scc Search Path" );
                return;
            }

            CreateProcess();
        }

        public void Shutdown()
        {
            lock ( _sync )
            {
                if ( _process != null )
                {
                    if ( IsRunning )
                        _process.Kill();

                    _process.OutputDataReceived -= Process_OutputDataReceived;
                    _process.ErrorDataReceived -= Process_ErrorDataReceived;
                    _process.Exited -= Process_Exited;

                    WriteLog( $"{PathToExe} with id {_process.Id} has exited" );

                    _process = null;
                }

                PathToExe = null;
            }
        }

        public void ResetSearchData()
        {
            SearchData = new Locator.Data() { Filename = "scc.exe" };
        }

        #endregion Public Methods

        #region Private Methods

        private bool HasExited()
        {
            lock ( _sync )
            {
                return _process?.HasExited != false;
            }
        }

        private bool FindCompilerExecutablePath()
        {
            if ( PathToExe != null )
                return true;

            PathToExe = Locator.Locate( SearchData );

            CouldNotFindExe = PathToExe == null;

            return !CouldNotFindExe;
        }

        private bool CreateProcess()
        {
            lock ( _sync )
            {
                if ( _process != null )
                    return true;

                Debug.Assert( !IsProcessBeingCreated );
                Debug.Print( $"Creating launcher process on thread: {Thread.CurrentThread.ManagedThreadId}" );

                IsProcessBeingCreated = true;

                _process = new Process();

                _process.StartInfo.FileName = PathToExe;
                _process.StartInfo.Arguments = $"-interactive -tcp -port {Port} -parent {Process.GetCurrentProcess().Id}";
                _process.StartInfo.CreateNoWindow = true;
                _process.StartInfo.RedirectStandardOutput = true;
                _process.StartInfo.RedirectStandardError = true;
                _process.StartInfo.UseShellExecute = false;
                _process.EnableRaisingEvents = true;
                _process.OutputDataReceived += Process_OutputDataReceived;
                _process.ErrorDataReceived += Process_ErrorDataReceived;
                _process.Exited += Process_Exited;

                if ( _process.Start() )
                {
                    WriteLog( $"{PathToExe} Started with process id: {_process.Id}" );

                    try
                    {
                        if ( _process.HasExited == true )
                        {
                            WriteLog( $"{PathToExe} exited immediately with code {_process.ExitCode} (0x{_process.ExitCode:X}) - it's probably corrupt or broken in some way" );
                            Shutdown();
                        }
                        else
                        {
                            _process.BeginOutputReadLine();
                            _process.BeginErrorReadLine();
                        }
                    }
                    catch ( Exception ex )
                    {
                        WriteLog( $"There was a problem trying to start {PathToExe}:\n{ex}" );
                        _process = null;
                    }

                }
                else
                {
                    Shutdown();
                }

                IsProcessBeingCreated = false;

                return _process != null;
            }
        }

        private bool IsRunningInternal()
        {
            lock (_sync)
            {
                return _process != null;
            }
        }

        #endregion Private Methods

        #region Event Handlers

        private void Process_OutputDataReceived( object sender, DataReceivedEventArgs e )
        {
            WriteLog( $"{e.Data}" );
        }

        private void Process_ErrorDataReceived( object sender, DataReceivedEventArgs e )
        {
            WriteLog( $"{e.Data}" );
        }

        private void Process_Exited( object sender, EventArgs e )
        {
            if( !IsProcessBeingCreated )
               Shutdown();
        }

        #endregion Event Handlers
    }
}
